// 日志记录模块
package xlog

import (
	"strings"
	"time"

	"gorm.io/gorm"
)

// 日志记录模块使用GO携程进行日志记录（此形式可能会存在退出的时候谢程未全部退出的问题，该问题会造成部分日志记录失败的情况）
// 错误等级：Info -> Notice -> Warning -> Error -> Crit -> Alert -> Panic
// 错误等级简写：I -> N -> W -> E -> C -> A -> P
// 备注：此等级中，Crit、Alert、Panic错误为影响到程序正常运行的错误，Error为需要注意，即将影响到正常运行的错误
//
//	Panic等级的错误也只是记录到数据库中，而不是直接将程序进行panic退出
//	记录函数的执行时常
//	调用方法为：defer xlog.FTime(time.Now())
//
// 配置项
type Config struct {
	DB                *gorm.DB                                               // 数据库连接
	Console           bool                                                   // 是否输出到控制台中
	TableName         string                                                 // 日志记录的数据表
	QuestName         string                                                 // http请求记录
	RunDir            string                                                 // 程序所在目录
	FuncLogName       string                                                 // 函数耗时存储表
	MaxTimes          uint                                                   // 日志中存储日志的最大天数，默认30天
	OldTableName      string                                                 // 默认旧的日志表名称，默认为[log_old]
	OldFuncLogName    string                                                 // 默认旧的函数耗时存储表名称，默认为[log_func_runtime_old]
	OldQuestName      string                                                 // http请求记录历史记录表，默认为[log_quest_name]
	MaxMigrationLimit uint                                                   // 单次迁移最大条数，默认为10000(每次均需要迁移log和log_func，所以最多为MaxMigrationLimit*2条记录)
	InsertAfter       func(log *LogStruct, fun *FuncLog, quest *QuestStruct) // 日志回调【为了避免循环调用，此函数中禁止使用xlog.xxxx进行日志记录】
	lkFunc            bool                                                   // 流控程序是否已启动
	LkInsertAfterNum  int                                                    // 每秒钟回调日志处理函数次数，超过该次数将会丢弃【避免xlog循环记录，默认值为10】
}

// 默认配置
var _default Config = Config{
	TableName:         "log",
	FuncLogName:       "log_func_runtime",
	MaxTimes:          30,
	OldTableName:      "log_old",
	OldFuncLogName:    "log_func_runtime_old",
	MaxMigrationLimit: 10000,
	QuestName:         "log_quest",
	OldQuestName:      "log_quest_name",
	LkInsertAfterNum:  10,
}

// 注入配置项
func Regedit(c *Config) {
	if c == nil {
		return
	}
	if _default.DB == nil && c.DB != nil {
		_default.DB = c.DB
	}
	if c.TableName != "" {
		_default.TableName = c.TableName
	}
	if c.RunDir != "" {
		_default.RunDir = strings.ReplaceAll(c.RunDir, "\\", "/")
	}
	if c.FuncLogName != "" {
		_default.FuncLogName = c.FuncLogName
	}
	if c.MaxTimes != 0 {
		_default.MaxTimes = c.MaxTimes
	}
	if c.MaxMigrationLimit != 0 {
		_default.MaxMigrationLimit = c.MaxMigrationLimit
	}
	if c.OldTableName != "" {
		_default.OldTableName = c.OldTableName
	}
	if c.OldFuncLogName != "" {
		_default.OldFuncLogName = c.OldFuncLogName
	}
	if c.QuestName != "" {
		_default.QuestName = c.QuestName
	}
	if c.QuestName != "" {
		_default.QuestName = c.QuestName
	}
	if c.OldQuestName != "" {
		_default.OldQuestName = c.OldQuestName
	}
	if c.InsertAfter != nil {
		_default.InsertAfter = c.InsertAfter
	}
	_default.Console = c.Console
	if _default.DB != nil {
		// 数据库/表注册/创建
		_default.DB.
			Set("gorm:table_options", "ENGINE=MyISAM").
			AutoMigrate(&LogStruct{}, &FuncLog{}, &QuestStruct{},
				&oldLogStruct{}, &oldFuncLog{}, &OldQuestStruct{},
			)
	}
	if c.LkInsertAfterNum != 0 {
		_default.LkInsertAfterNum = c.LkInsertAfterNum
	}
	go lkFuncStart()
	// 注册定时脚本，以便定时迁移老旧日志到old数据库中
	// 因此服务无法在此处导入，若需要的话请在外部进行导入到xcron中
	// xcron.Spec(_default.OldSpec, xcron.CronTab{
	// 	Key:  "xgotool.xlog.log.migration",
	// 	Name: "日志迁移服务",
	// 	Desc: "定时执行日志迁移服务，避免日志表中的数据量过大导致查询速度缓慢问题【此操作会一定程度影响日志的查询】",
	// 	Func: LogMigration,
	// })
}

// 错误信息统计表
type LogStruct struct {
	Id        uint64 `gorm:"column:id;primaryKey;type:BIGINT UNSIGNED;not null;autoIncrement" json:"id" form:"id"` // 条目ID
	Type      string `gorm:"column:type;type:CHAR(1);comment:错误类型;index:error_type" json:"type" form:"type"`       //错误类型，如：I、D等
	File      string `gorm:"column:file;type:VARCHAR(200);comment:错误文件" json:"file" form:"file"`                   //错误文件
	Line      uint   `gorm:"column:line;type:INT UNSIGNED;comment:文件行数;index:error_type" json:"line" form:"line"`  //错误行数
	Msg       string `gorm:"column:msg;type:VARCHAR(200);comment:消息概述" json:"msg" form:"msg"`                      //错误消息概述
	Content   string `gorm:"column:content;type:TEXT;comment:错误消息内容" json:"content" form:"content"`                //错误信息内容
	CreatedAt string `gorm:"column:created_at;type:DATETIME;comment:创建时间" json:"created_at" form:"created_at"`     //错误发生时间
}

// 获取表名
func (c *LogStruct) TableName() string {
	return _default.TableName
}

// 错误信息统计表
type oldLogStruct struct {
	Id        uint64 `gorm:"column:id;primaryKey;type:BIGINT UNSIGNED;not null;autoIncrement" json:"id" form:"id"` // 条目ID
	Type      string `gorm:"column:type;type:CHAR(1);comment:错误类型;index:error_type" json:"type" form:"type"`       //错误类型，如：I、D等
	File      string `gorm:"column:file;type:VARCHAR(200);comment:错误文件" json:"file" form:"file"`                   //错误文件
	Line      uint   `gorm:"column:line;type:INT UNSIGNED;comment:文件行数;index:error_type" json:"line" form:"line"`  //错误行数
	Msg       string `gorm:"column:msg;type:VARCHAR(200);comment:消息概述" json:"msg" form:"msg"`                      //错误消息概述
	Content   string `gorm:"column:content;type:TEXT;comment:错误消息内容" json:"content" form:"content"`                //错误信息内容
	CreatedAt string `gorm:"column:created_at;type:DATETIME;comment:创建时间" json:"created_at" form:"created_at"`     //错误发生时间
}

// 获取表名
func (c *oldLogStruct) TableName() string {
	return _default.OldTableName
}

// 函数耗时日志表
type FuncLog struct {
	Id        uint64 `gorm:"column:id;primaryKey;type:BIGINT UNSIGNED;not null;autoIncrement" json:"id" form:"id"` // 条目ID
	Func      string `gorm:"column:func;type:VARCHAR(200);comment:函数名称" json:"func" form:"func"`                   //函数名称
	Line      uint   `gorm:"column:line;type:INT UNSIGNED;comment:调用行数" json:"line" form:"line"`                   //调用行数
	Runtime   uint64 `gorm:"column:runtime;type:BIGINT UNSIGNED;comment:耗时：ns" json:"runtime" form:"runtime"`      // 耗时：ns
	CreatedAt string `gorm:"column:created_at;type:DATETIME;comment:调用时间" json:"created_at" form:"created_at"`     //调用时间
}

// 获取表名
func (c *FuncLog) TableName() string {
	return _default.FuncLogName
}

// 函数耗时日志表
type oldFuncLog struct {
	Id        uint64 `gorm:"column:id;primaryKey;type:BIGINT UNSIGNED;not null;autoIncrement" json:"id" form:"id"` // 条目ID
	Func      string `gorm:"column:func;type:VARCHAR(200);comment:函数名称" json:"func" form:"func"`                   //函数名称
	Line      uint   `gorm:"column:line;type:INT UNSIGNED;comment:调用行数" json:"line" form:"line"`                   //调用行数
	Runtime   uint64 `gorm:"column:runtime;type:BIGINT UNSIGNED;comment:耗时：ns" json:"runtime" form:"runtime"`      // 耗时：ns
	CreatedAt string `gorm:"column:created_at;type:DATETIME;comment:调用时间" json:"created_at" form:"created_at"`     //调用时间
}

// 获取表名
func (c *oldFuncLog) TableName() string {
	return _default.OldFuncLogName
}

// https请求日志记录
type QuestStruct struct {
	Id        uint   `gorm:"column:id;type:int(10) unsigned;primaryKey;autoIncrement;not null" form:"id" json:"id"`
	Uri       string `gorm:"column:uri;type:varchar(200);comment:HTTP请求网址" form:"uri" json:"uri"`                                  //HTTP请求网址
	Param     string `gorm:"column:param;type:longtext;comment:请求参数[JSON格式存储]" form:"param" json:"param"`                          //请求参数[JSON格式存储]
	HttpCode  int    `gorm:"column:http_code;type:int(11);not null;comment:HTTP请求返回的Code值" form:"http_code" json:"http_code"`      //HTTP请求返回的Code值
	Body      string `gorm:"column:body;type:longtext;comment:HTTP请求返回值" form:"body" json:"body"`                                  //HTTP请求返回值
	Error     string `gorm:"column:error;type:varchar(200);comment:HTTP请求报错信息" form:"error" json:"error"`                          //HTTP请求报错信息
	Header    string `gorm:"column:header;type:text;comment:HTTP请求时发送的Header请求头" form:"header" json:"header"`                      //HTTP请求时发送的Header请求头
	StartTime string `gorm:"column:start_time;type:datetime;comment:HTTP请求开始时间" form:"start_time" json:"start_time"`               //HTTP请求开始时间
	EndTime   string `gorm:"column:end_time;type:datetime;comment:HTTP请求结束时间" form:"end_time" json:"end_time"`                     //HTTP请求结束时间
	QuestSec  uint   `gorm:"column:quest_sec;type:int(10) unsigned;not null;comment:请求耗时 单位：毫秒" form:"quest_sec" json:"quest_sec"` //请求耗时 单位：毫秒
	ClientIp  string `gorm:"column:client_ip;type:varchar(60);not null;comment:客户端IP地址，兼容IPV6" form:"client_ip" json:"client_ip"`  // 客户端IP地址，兼容IPV6
}

// 返回所属表名信息
func (c *QuestStruct) TableName() string {
	return _default.QuestName
}

// https请求日志记录
type OldQuestStruct struct {
	Id        uint   `gorm:"column:id;type:int(10) unsigned;primaryKey;autoIncrement;not null" form:"id" json:"id"`
	Uri       string `gorm:"column:uri;type:varchar(200);comment:HTTP请求网址" form:"uri" json:"uri"`                                  //HTTP请求网址
	Param     string `gorm:"column:param;type:longtext;comment:请求参数[JSON格式存储]" form:"param" json:"param"`                          //请求参数[JSON格式存储]
	HttpCode  int    `gorm:"column:http_code;type:int(11);not null;comment:HTTP请求返回的Code值" form:"http_code" json:"http_code"`      //HTTP请求返回的Code值
	Body      string `gorm:"column:body;type:longtext;comment:HTTP请求返回值" form:"body" json:"body"`                                  //HTTP请求返回值
	Error     string `gorm:"column:error;type:varchar(200);comment:HTTP请求报错信息" form:"error" json:"error"`                          //HTTP请求报错信息
	Header    string `gorm:"column:header;type:text;comment:HTTP请求时发送的Header请求头" form:"header" json:"header"`                      //HTTP请求时发送的Header请求头
	StartTime string `gorm:"column:start_time;type:datetime;comment:HTTP请求开始时间" form:"start_time" json:"start_time"`               //HTTP请求开始时间
	EndTime   string `gorm:"column:end_time;type:datetime;comment:HTTP请求结束时间" form:"end_time" json:"end_time"`                     //HTTP请求结束时间
	QuestSec  uint   `gorm:"column:quest_sec;type:int(10) unsigned;not null;comment:请求耗时 单位：毫秒" form:"quest_sec" json:"quest_sec"` //请求耗时 单位：毫秒
	ClientIp  string `gorm:"column:client_ip;type:varchar(60);not null;comment:客户端IP地址，兼容IPV6" form:"client_ip" json:"client_ip"`  // 客户端IP地址，兼容IPV6
}

// 返回所属表名信息
func (c *OldQuestStruct) TableName() string {
	return _default.OldQuestName
}

// 日志记录次数信息[同一秒超过10次循环调用的话就直接返回，不再进行记录]
var insertLogCount int = _default.LkInsertAfterNum

// 日志插入回调函数
//
//	log		正常的日志结构体，nil表示非正常日志记录
//	fun		函数耗时日志结构体，nil表示非正常函数耗时日志
//	quest	Quest请求日志结构体，nil表示非正常请求日志
func insertLog(log *LogStruct, fun *FuncLog, quest *QuestStruct) {
	if insertLogCount <= 0 {
		return
	}
	insertLogCount--
	if _default.InsertAfter != nil {
		// 因存储时部分位置已经使用了go关键词进行携程开辟，所以此处不再进行携程开辟
		_default.InsertAfter(log, fun, quest)
	}
}

// 启动流控程序
func lkFuncStart() {
	if _default.lkFunc {
		// 不重复启动流控程序
		return
	}
	_default.lkFunc = true
	ticker := time.NewTicker(time.Second)
	for t := range ticker.C {
		_ = t
		insertLogCount = _default.LkInsertAfterNum
	}
}
